//---------------------------------------------------------------------------------
// Copyright (c) 2008 VoiceLynx Project
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//     1. The origin of this software must not be misrepresented; you must not
//        claim that you wrote the original software. If you use this software
//        in a product, an acknowledgment in the product documentation would be
//        appreciated but is not required.
//
//     2. Altered source versions must be plainly marked as such, and must not be
//        misrepresented as being the original software.
//
//     3. This notice may not be removed or altered from any source
//        distribution.
//---------------------------------------------------------------------------------

#include "VLCodec.h"

#include "VLSoundManager.h"

#define SPEEX_QUALITY 3



VLSpeexDSP::VLSpeexDSP(VLU32 frameSize, VLU32 sampleRate)
{
  mState = speex_preprocess_state_init(frameSize, sampleRate);

  VL32 denoise = 1;
  VL32 agc = 1;
  //VL32 vad = 1;
  //VL32 dereverb;
  
  speex_preprocess_ctl(mState, SPEEX_PREPROCESS_SET_DENOISE, &denoise);
  speex_preprocess_ctl(mState, SPEEX_PREPROCESS_SET_AGC, &agc);
  //speex_preprocess_ctl(mState, SPEEX_PREPROCESS_SET_VAD, &vad);


  //speex_preprocess_ctl(mState, SPEEX_PREPROCESS_GET_DEREVERB, &dereverb);

  //VL_TRACE(vad);
}

VLSpeexDSP::~VLSpeexDSP()
{
  speex_preprocess_state_destroy(mState);
}

bool VLSpeexDSP::process(VL16 *in, bool generateOutput)
{
  bool speaking = true;

  if (generateOutput)
  {
    int returnVal = speex_preprocess_run(mState, in);

    if (returnVal == 0)
    {
      speaking = false;
    }
  }
  else
  {
    speex_preprocess_estimate_update(mState, in);
  }

  //std::cout << ((speaking ? "TRUE" : "FALSE")) << std::endl;

  printf("Speaking: %d\n", speaking);

  return speaking;
}












VLSpeexCodec::VLSpeexCodec()
{

}

VLEncoderPtr VLSpeexCodec::createEncoder()
{
  return VLEncoderPtr(new VLSpeexEncoder());
}

VLDecoderPtr VLSpeexCodec::createDecoder()
{
  return VLDecoderPtr(new VLSpeexDecoder());
}

VLSpeexEncoder::VLSpeexEncoder()
: mEncodeBuffer(16384),
  mToSendBuffer(16384)
{
  int param = 0;

  // init the encoder and the speex bits structure
  mEncodeState = speex_encoder_init(&speex_nb_mode);
  speex_bits_init(&mEncodeBits);

  // set the quality
  param = SPEEX_QUALITY;
  speex_encoder_ctl(mEncodeState, SPEEX_SET_QUALITY, &param);
  
  // get the frame size in samples
  int samplesPerFrame;
  speex_encoder_ctl(mEncodeState, SPEEX_GET_FRAME_SIZE, &samplesPerFrame);

  // create a buffer to hold a single frame to encode
  mSpeexFrameSize = samplesPerFrame * VL_FMOD_SAMPLE_SIZE_IN_BYTES;
  mSpeexFrame = new VLByte[mSpeexFrameSize];

  mPreprocessor.reset(new VLSpeexDSP(samplesPerFrame, VL_FMOD_SMAPLE_RATE));
}

VLSpeexEncoder::~VLSpeexEncoder()
{
  // destory the encoder and speex bits struct
  speex_encoder_destroy(mEncodeState);
  speex_bits_destroy(&mEncodeBits);

  // delete the frame-sized buffer
  delete[] mSpeexFrame;
}

VLByteArrayPtr VLSpeexEncoder::encode(VLByteArrayPtr in)
{
  // The first thing we do with incoming data is we throw it into a circular
  // buffer, because speex requires data to be in specifically sized chunks
  if (in.get() && in->size() > 0)
  {
    mEncodeBuffer.append(&(*in.get())[0], in->size());
  }

  // Rather than do many appends into the to send buffer, we will use the largeBuffer
  // and then do a single append
  int bufferWritePtr = 0;

  // Encode the data
  while (mEncodeBuffer.pending() >= mSpeexFrameSize)
  {
    // Pull data to encode
    mEncodeBuffer.consume(mSpeexFrame, mSpeexFrameSize);

    // Do the encoding
    speex_bits_reset(&mEncodeBits);
    speex_encode_int(mEncodeState, (VL16*) mSpeexFrame, &mEncodeBits);

    // Extract the bytes that need to be sent across the wire
    int nBytes = speex_bits_write(&mEncodeBits, (char *) &mLargeBuffer[bufferWritePtr], 512);

    bufferWritePtr += nBytes;
  }

  VLByteArrayPtr returnVal;

  // NOTE: IF YOU DON'T CARE ABOUT MINIMUM PACKET SIZES
  // Return all the data we have prepared to send
  if (bufferWritePtr > 0)
  {
    returnVal.reset(new VLByteArray(bufferWritePtr));

    memcpy(&(*returnVal.get())[0], mLargeBuffer, bufferWritePtr);

    // NOTE: IF YOU CARE ABOUT MINIMUM PACKET SIZES
    //mToSendBuffer.append(mLargeBuffer, bufferWritePtr);

    //VLByteArrayPtr returnVal(new VLByteArray(0));

    //if (mToSendBuffer.pending() > 256)
    //{
    //  returnVal = mToSendBuffer.takeAll();
    //}
    //return mToSendBuffer.takeAll();

  }

  return returnVal;
}

VLSpeexDecoder::VLSpeexDecoder()
: mDecodeBuffer(16384)
{
  int param = 0;

  // init the decoder and speex bits structure
  mDecodeState = speex_decoder_init(&speex_nb_mode);
  speex_bits_init(&mDecodeBits);

  // set the quality
  //param = SPEEX_QUALITY;
  //speex_decoder_ctl(mDecodeState, SPEEX_SET_QUALITY, &param);

  // get the frame size in samples
  int decFS;
  speex_decoder_ctl(mDecodeState, SPEEX_GET_FRAME_SIZE, &decFS);

  // create a buffer to hold a single frame to decode into
  mSpeexFrameSize = decFS * VL_FMOD_SAMPLE_SIZE_IN_BYTES;
  mSpeexFrame = new VLByte[mSpeexFrameSize];
}

VLSpeexDecoder::~VLSpeexDecoder()
{
  // destroy the decoder
  speex_decoder_destroy(mDecodeState);
  speex_bits_destroy(&mDecodeBits);

  // delete the frame-sized buffer
  delete[] mSpeexFrame;
}

VLByteArrayPtr VLSpeexDecoder::decode(VLByteArrayPtr in)
{
  VLByteArrayPtr returnVal;

  // If we have data
  if (in->size() > 0)
  {
    // Clear out the speex bits struct
    speex_bits_reset(&mDecodeBits);

    // Bring in our packet and decode it
    speex_bits_read_from(&mDecodeBits, (VL8 *) &(*in.get())[0], in->size());

    // For each frame that we find, append it to the decode buffer
    while (speex_decode_int(mDecodeState, &mDecodeBits, (VL16 *) mSpeexFrame) == 0)
    {
      mDecodeBuffer.append(mSpeexFrame, mSpeexFrameSize);
    }

    // Return all the decoded data
    returnVal = mDecodeBuffer.takeAll();
  }

  return returnVal;
}
